package sketch.specs.annotation;

import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import sketch.util.Checker;

public class AnnotationProcessor {
	
	public final Class<?>[] classes;
	
	public Map<Class<?>, PClass> cached_result = null;
	
	public AnnotationProcessor(Class<?>...classes) {
		Checker.checkNull(classes, "The classes should not be null.");
		this.classes = classes;
	}
	
	public void process() {
		if(this.cached_result != null) {
			return;// this.cached_result;
		}
		this.cached_result = new LinkedHashMap<Class<?>, PClass>();
		for(Class<?> clz : this.classes) {
			PClass pclass = new PClass(clz);
			pclass.process_methods();
			if(pclass.methods.isEmpty()) {
				continue;
			}
			cached_result.put(clz, pclass);
		}
	}
	
	public List<Map<String, String>> getMappings(Method method) {
		Class<?> clz = method.getDeclaringClass();
		if(this.cached_result == null) {
			this.process();
		}
		if(!this.cached_result.containsKey(clz)) {
			return null;
		}
		PClass pclass = this.cached_result.get(clz);
		PMethod pm = null;
		for(PMethod pmethod : pclass.methods) {
			if(pmethod.method.toGenericString().equals(method.toGenericString())) {
				pm = pmethod;
				break;
			}
		}
		if(pm == null) {
			return null;
		}
		return pm.mapping_list;
	}
	
}

class PClass {
	public final List<PMethod> methods;
	
	public final Class<?> clz;
	
	public PClass(Class<?> clazz) {
		this.methods = new LinkedList<PMethod>();
		this.clz = clazz;
	}
	
	public void process_methods() {
		for(Method m : this.clz.getDeclaredMethods()) {
			if(m.isAnnotationPresent(Repl.class)) {
			  Repl rep = m.getAnnotation(Repl.class);
			  PMethod pmethod = new PMethod(m);
			  pmethod.addMappings(rep.source(), rep.dest());
			  //skip those empty ones
			  if(!pmethod.mapping_list.isEmpty()) {
				this.methods.add(pmethod);
			  }
			} else if(m.isAnnotationPresent(Repls.class)) {
				PMethod pmethod = new PMethod(m);
				Repls reps = m.getAnnotation(Repls.class);
				Repl[] rs = reps.repls();
				for(Repl r : rs) {
					pmethod.addMappings(r.source(), r.dest());
				}
				//skip those empty ones
				if(!pmethod.mapping_list.isEmpty()) {
					this.methods.add(pmethod);
				}
			}
		}
	}
}

class PMethod {
	public final List<Map<String, String>> mapping_list = new LinkedList<Map<String, String>>();
	
	public final Method method;
	
	public PMethod(Method method) {
		this.method = method;
	}
	
	void addMappings(String[] sources, String[] dests) {
		if(sources.length == 0 || dests.length == 0) {
			return;
		}
		Checker.checkTrue(sources.length == dests.length, "The length should be equal.");
		Map<String, String> map = new LinkedHashMap<String, String>();
		for(int i = 0; i < sources.length; i++) {
			Checker.checkNull(sources[i], "Sources could not be null.");
			Checker.checkNull(dests[i], "Dest could not be null.");
			map.put(sources[i], dests[i]);
		}
		mapping_list.add(map);
	}
}